package com.xxr.mom.ci.charging.interworking.base;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.xxr.mom.ci.charging.interworking.dto.InterworkingConfigDto;
import com.xxr.mom.ci.charging.interworking.entity.InterworkingConfig;
import com.xxr.mom.ci.charging.interworking.vo.api.AccessTokenRequest;
import com.xxr.mom.ci.charging.interworking.vo.api.AccessTokenResponse;
import com.xxr.mom.ci.charging.message.MessageChannel;
import com.xxr.mom.ci.common.dto.InterworkingParam;
import com.xxr.mom.ci.common.enums.InterworkingRetEnum;
import com.xxr.mom.ci.common.exception.InterworkingException;
import com.xxr.mom.ci.common.log.LogTrace;
import com.xxr.mom.ci.common.util.*;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.ThreadContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.Date;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author natsuki_kining
 */
@Log4j2
public abstract class AbstractService<R, P> implements InterfaceAccepter<R, P>, InterfaceSender<R, P> {

    @Autowired
    protected RedisTemplate<String, String> tokenRedisTemplate;
    @Autowired
    protected MessageChannel messageChannel;

    @Override
    public void validateToken(String token, InterworkingParam interworkingParam, InterworkingConfigDto configDto) {
        if (StringUtils.isBlank(token)) {
            throw new InterworkingException(InterworkingRetEnum.TOKEN_ERROR);
        }
        String redisKey = Constant.RedisKey.REDIS_TOKEN_ACCEPT + interworkingParam.getOperatorId();
        String redisValue = tokenRedisTemplate.opsForValue().get(redisKey);
        if (Objects.isNull(redisValue)) {
            throw new InterworkingException(InterworkingRetEnum.TOKEN_ERROR);
        }
        if (!token.equals(Constant.RedisKey.BEARER + redisValue)) {
            throw new InterworkingException(InterworkingRetEnum.TOKEN_ERROR);
        }
    }

    @Override
    public P dataHandler(R r, InterworkingConfigDto configDto) {
        return null;
    }

    @Override
    public R jsonObjectToRequestParam(JSONObject jsonObject) {
        Class<?> clazz = ReflectionUtil.findSuperClassParameterType(this, 0);
        return (R) JsonUtil.parseObject(jsonObject.toJSONString(), clazz);
    }

    @Override
    public P send(R r, InterworkingConfigDto configDto) {
        if (StringUtils.isBlank(ThreadContext.get(LogTrace.TRACE_ID))) {
            ThreadContext.put(LogTrace.TRACE_ID, RandomUtil.randomString(16));
        }
        validateRequestParam(r);
        InterworkingConfig interworkingConfig = configDto.getInterworkingConfig();
        String url = interworkingConfig.getOperatorUrl() + configDto.getHttpClientMethodName();
        InterworkingParam interworkingParam = this.getInterworkingParam(r, configDto.getInterworkingConfig());
        log.info("请求地址：{}", url);
        log.info("请求数据明文：{}", JsonUtil.toJSONString(r));
        log.info("请求数据密文：{}", JsonUtil.toJSONString(interworkingParam));
        String accessToken = this.getSenderAccessToken(configDto);
        String postResult;
        try {
            postResult = HttpUtil.post(url, JsonUtil.toJSONString(interworkingParam),  accessToken);
        } catch (IORuntimeException e) {
            log.error(e.getMessage(), e);
            // TODO 重试 判断是否需要重试，重试次数
            messageChannel.rePush(r, configDto, 3);
            throw e;
        }
        log.info("请求结果密文：{}", postResult);
        P p = (P) decodeToP(interworkingConfig.getDecode(), postResult);
        log.info("请求结果明文：{}", JsonUtil.toJSONString(p));
        return p;
    }

    @Override
    public String getSenderAccessToken(InterworkingConfigDto configDto) {
        InterworkingConfig interworkingConfig = configDto.getInterworkingConfig();
        String redisTokenKey = Constant.RedisKey.REDIS_TOKEN_SEND + interworkingConfig.getOperatorId();
        String accessToken = tokenRedisTemplate.opsForValue().get(redisTokenKey);

        if (StringUtils.isNotBlank(accessToken)) {
            return accessToken;
        }

        AccessTokenRequest accessTokenRequest = new AccessTokenRequest();
        accessTokenRequest.setOperatorID(interworkingConfig.getOwnerId());
        accessTokenRequest.setOperatorSecret(interworkingConfig.getOwnerSecret());
        InterworkingParam interworkingParam = getInterworkingParam(interworkingConfig, configDto.getInterworkingConfig());

        String postResult = HttpUtil.post(interworkingConfig.getOperatorUrl() + configDto.getQueryTokenMethod(), JsonUtil.toJSONString(interworkingParam));
        log.info("请求对方query_token接口参数：{}", JsonUtil.toJSONString(interworkingParam));
        log.info("请求对方query_token接口结果：{}", postResult);

        AccessTokenResponse accessTokenResponse = (AccessTokenResponse) EncryptionDecryptionUtil.decodeObject(interworkingConfig.getDecode(), postResult, AccessTokenResponse.class);
        if (!Objects.equals(0, accessTokenResponse.getSucStat())) {
            log.warn("获取token失败");
            throw new InterworkingException(InterworkingRetEnum.TOKEN_ERROR);
        }
        accessToken = accessTokenResponse.getAccessToken();

        long tokenAvailableTime = accessTokenResponse.getTokenAvailableTime() - 60;
        tokenRedisTemplate.opsForValue().set(redisTokenKey, accessToken, tokenAvailableTime, TimeUnit.SECONDS);

        return accessToken;
    }

    protected InterworkingParam getInterworkingParam(Object data, InterworkingConfig interworkingConfig) {
        InterworkingParam interworkingParam = new InterworkingParam();
        interworkingParam.setOperatorId(interworkingConfig.getOwnerId());
        interworkingParam.setData(EncryptionDecryptionUtil.encrypt(interworkingConfig.getEncrypt(), JSON.toJSONString(data)));
        interworkingParam.setTimeStamp(DateUtil.format(new Date(), "yyyyMMddHHmmss"));
        interworkingParam.setSeq(EncryptionDecryptionUtil.getSeq());
        interworkingParam.setSig(EncryptionDecryptionUtil.sign(interworkingConfig.getOperatorSigSecret(), interworkingParam.getOperatorId() + interworkingParam.getData() + interworkingParam.getTimeStamp() + interworkingParam.getSeq()));
        return interworkingParam;
    }
}
